﻿// Fill out your copyright notice in the Description page of Project Settings.

#include "Widget/SElementUIBorder.h"
#include "SlateOptMacros.h"

#define LOCTEXT_NAMESPACE "ElementUI"

void SElementUIBorder::Construct(const FArguments& InArgs)
{
	// // UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__));
	LineType = InArgs._LineType;
	LineColor = InArgs._LineColor;
	LineThickness = InArgs._LineThickness;
	DashedLength = InArgs._DashedLength;
	DashedInterval = InArgs._DashedInterval;
	DashedOffset = InArgs._DashedOffset;
	FilletRadius = InArgs._FilletRadius;

	ChildSlot
	.HAlign(InArgs._HAlign)
	.VAlign(InArgs._VAlign)
	.Padding(InArgs._Padding)
	[
		InArgs._Content.Widget
	];
}

FVector2D SElementUIBorder::ComputeDesiredSize(float LayoutScaleMultiplier) const
{
	// // UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__));
	return FVector2D(1, 1) * SCompoundWidget::ComputeDesiredSize(LayoutScaleMultiplier);
}

int32 SElementUIBorder::OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry,
                                const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements,
                                int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const
{
	// // UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__));
	// FSlateRect SnappedClippingRect = FSlateRect(FMath::RoundToInt(MyCullingRect.Left), FMath::RoundToInt(MyCullingRect.Top), FMath::RoundToInt(MyCullingRect.Right), FMath::RoundToInt(MyCullingRect.Bottom));
	// // UE_LOG(LogTemp, Warning, TEXT("Position: X %f   Y %f"),AllottedGeometry.Position.X,AllottedGeometry.Position.Y);
	// // UE_LOG(LogTemp, Warning, TEXT("AbsolutePosition: X %f   Y %f"),AllottedGeometry.AbsolutePosition.X,AllottedGeometry.AbsolutePosition.Y);
	// // UE_LOG(LogTemp, Warning, TEXT("Scale: %f"),AllottedGeometry.Scale);
	// // UE_LOG(LogTemp, Warning, TEXT("Size: X %f   Y %f"),AllottedGeometry.Size.X,AllottedGeometry.Size.Y);
	
	DrawBroder(OutDrawElements, LayerId, AllottedGeometry,InWidgetStyle);
	return SCompoundWidget::OnPaint(Args, AllottedGeometry, MyCullingRect, OutDrawElements, LayerId, InWidgetStyle,bParentEnabled);;
}

void SElementUIBorder::SetContentPadding(const FMargin& value)
{
	TAttribute<FMargin> inPadding = value;
	// SetAttribute(ChildSlot.SlotPadding, inPadding, EInvalidateWidgetReason::Layout);
}

void SElementUIBorder::SetHAlign(EHorizontalAlignment value)
{
	ChildSlot.HAlign(value);
	Invalidate(EInvalidateWidget::Layout);
}

void SElementUIBorder::SetVAlign(EVerticalAlignment value)
{
	ChildSlot.VAlign(value);
	Invalidate(EInvalidateWidget::Layout);
}

void SElementUIBorder::SetContent(TSharedRef<SWidget> InContent)
{
	ChildSlot
	[
		InContent
	];
}

FReply SElementUIBorder::OnMouseMove(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
{
	// bool WhatToReturn = true;
	if(FilletRadius <= 0) return SWidget::OnMouseMove(MyGeometry, MouseEvent);
	FVector2D LocalPosition = MyGeometry.AbsoluteToLocal(MouseEvent.GetScreenSpacePosition());
	LocalPosition.X = floor(LocalPosition.X);
	LocalPosition.Y = floor(LocalPosition.Y);
	FVector2D WidgetSize = MyGeometry.GetLocalSize();
	if(!IsPointInside(LocalPosition, WidgetSize))
	{
		bIsHovered = false;
		return FReply::Unhandled();
	}
	bIsHovered = true;
	return SWidget::OnMouseMove(MyGeometry, MouseEvent);
	// LocalPosition /= MyGeometry.GetLocalSize();
	// int ImageWidth = AdvancedHitTexture->PlatformData->SizeX;
	// LocalPosition.X *= ImageWidth;
	// LocalPosition.Y *= AdvancedHitTexture->PlatformData->SizeY;
	// int BufferPosition = (LocalPosition.Y * ImageWidth) + LocalPosition.X;
	// FColor* ImageData = static_cast<FColor*>((AdvancedHitTexture->PlatformData->Mips[0]).BulkData.Lock(LOCK_READ_ONLY));
	// if (!ImageData) { WhatToReturn = false; }
	// else { if (ImageData[BufferPosition].A <= AdvancedHitAlpha) WhatToReturn = false; }
	// AdvancedHitTexture->PlatformData->Mips[0].BulkData.Unlock();
	// if (WhatToReturn != bIsHovered) {
	// 	bIsHovered = WhatToReturn;
	// 	if (bIsHovered) SWidget::OnMouseEnter(MyGeometry, MouseEvent);
	// 	else SWidget::OnMouseLeave(MouseEvent);
	// }
}

FCursorReply SElementUIBorder::OnCursorQuery(const FGeometry& MyGeometry, const FPointerEvent& CursorEvent) const
{
	if (!bIsHovered) return FCursorReply::Unhandled();
	TOptional<EMouseCursor::Type> TheCursor = GetCursor();
	return (TheCursor.IsSet()) ? FCursorReply::Cursor(TheCursor.GetValue()) : FCursorReply::Unhandled();
}

void SElementUIBorder::SetLineColor(FLinearColor value)
{
	LineColor = value;
}

void SElementUIBorder::SetLineThickness(int32 value)
{
	LineThickness = value;
}

void SElementUIBorder::SetLineType(TEnumAsByte<EElementUIBorederLineType> value)
{
	LineType = value.GetValue();
}

void SElementUIBorder::SetDashedLength(float value)
{
	DashedLength = value;
}

void SElementUIBorder::SetDashedInterval(float value)
{
	DashedInterval = value;
}

void SElementUIBorder::SetDashedOffset(float value)
{
	DashedOffset = value;
}

void SElementUIBorder::SetFilletRadius(float value)
{
	FilletRadius = value;
}

int32 SElementUIBorder::DrawLine(FSlateWindowElementList& ElementList, uint32 InLayer,
                                 const FPaintGeometry& PaintGeometry, const TArray<FVector2D>& Points, const FWidgetStyle& InWidgetStyle) const
{
	FSlateDrawElement::MakeLines(ElementList, InLayer, PaintGeometry, Points, ESlateDrawEffect::None, LineColor.Get()*InWidgetStyle.GetColorAndOpacityTint(),
	                             false, LineThickness * PaintGeometry.DrawScale);
	return InLayer;
}

int32 SElementUIBorder::DrawBroder(FSlateWindowElementList& ElementList, uint32 InLayer,
                                   const FGeometry& AllottedGeometry, const FWidgetStyle& InWidgetStyle) const
{
	float filletRadius=LineType==EElementUIBorederLineType::Solid?FilletRadius:0.f;
	TArray<FVector2D> TopBorder;
	TopBorder.Init(FVector2D::ZeroVector, 2);
	TopBorder[0].X = 0 + filletRadius;
	TopBorder[0].Y = 0;
	TopBorder[1].X = AllottedGeometry.Size.X - filletRadius;
	TopBorder[1].Y = 0;

	TArray<FVector2D> RightBorder;
	RightBorder.Init(FVector2D::ZeroVector, 2);
	RightBorder[0].X = AllottedGeometry.Size.X;
	RightBorder[0].Y = 0 + filletRadius;
	RightBorder[1].X = AllottedGeometry.Size.X;
	RightBorder[1].Y = AllottedGeometry.Size.Y - filletRadius;

	TArray<FVector2D> BottomBorder;
	BottomBorder.Init(FVector2D::ZeroVector, 2);
	BottomBorder[0].X = AllottedGeometry.Size.X - filletRadius;
	BottomBorder[0].Y = AllottedGeometry.Size.Y;
	BottomBorder[1].X = 0 + filletRadius;
	BottomBorder[1].Y = AllottedGeometry.Size.Y;

	TArray<FVector2D> LeftBorder;
	LeftBorder.Init(FVector2D::ZeroVector, 2);
	LeftBorder[0].X = 0;
	LeftBorder[0].Y = AllottedGeometry.Size.Y - filletRadius;
	LeftBorder[1].X = 0;
	LeftBorder[1].Y = 0 + filletRadius;

	switch (LineType)
	{
	case EElementUIBorederLineType::Solid:
		if(filletRadius*2<AllottedGeometry.Size.Y)
		{
			DrawLine(ElementList, InLayer, AllottedGeometry.ToPaintGeometry(), RightBorder, InWidgetStyle);
			DrawLine(ElementList, InLayer, AllottedGeometry.ToPaintGeometry(), LeftBorder, InWidgetStyle);
		}
		if(filletRadius*2<AllottedGeometry.Size.X)
		{
			DrawLine(ElementList, InLayer, AllottedGeometry.ToPaintGeometry(), TopBorder, InWidgetStyle);
			DrawLine(ElementList, InLayer, AllottedGeometry.ToPaintGeometry(), BottomBorder, InWidgetStyle);
		}
		DrawSingleFillet(0.5*PI,-1,-1,ElementList,InLayer,AllottedGeometry,InWidgetStyle);
		DrawSingleFillet(0.5*PI,-1,1,ElementList,InLayer,AllottedGeometry,InWidgetStyle);
		DrawSingleFillet(0.5*PI,1,1,ElementList,InLayer,AllottedGeometry,InWidgetStyle);
		DrawSingleFillet(0.5*PI,1,-1,ElementList,InLayer,AllottedGeometry,InWidgetStyle);
		break;
	case EElementUIBorederLineType::Dashed:
		DrawSingleBorderLine(ElementList, InLayer, AllottedGeometry, TopBorder, InWidgetStyle);
		DrawSingleBorderLine(ElementList, InLayer, AllottedGeometry, RightBorder, InWidgetStyle);
		DrawSingleBorderLine(ElementList, InLayer, AllottedGeometry, BottomBorder, InWidgetStyle);
		DrawSingleBorderLine(ElementList, InLayer, AllottedGeometry, LeftBorder, InWidgetStyle);
		break;
	case EElementUIBorederLineType::Point:
		break;
	}
	return InLayer;
}

TArray<FVector2D> SElementUIBorder::LineLerp(FVector2D StartPoint, FVector2D EndPoint, float StartOffset) const
{
	TArray<FVector2D> result;
	if (StartPoint == EndPoint) return result;
	float lineLength = (EndPoint - StartPoint).Size();
	float startOffset = FMath::Fmod(StartOffset, DashedLength + DashedInterval);
	FVector2D lineEndPoint(EndPoint - StartPoint);
	int32 xAxisDir = lineEndPoint.X < 0 ? -1 : 1;
	int32 yAxisDir = lineEndPoint.Y < 0 ? -1 : 1;
	float k;
	float offsetX;
	float offsetY;
	float dashLengthOffsetX;
	float dashLengthOffsetY;
	float DashedIntervalOffsetX;
	float DashedIntervalOffsetY;
	if (lineEndPoint.X == 0)
	{
		offsetX = 0;
		offsetY = startOffset;
		dashLengthOffsetX = 0;
		dashLengthOffsetY = DashedLength;
		DashedIntervalOffsetX = 0;
		DashedIntervalOffsetY = DashedInterval;
	}
	else
	{
		k = lineEndPoint.Y / lineEndPoint.X;
		offsetX = startOffset / FMath::Sqrt(k * k + 1);
		offsetY = k * offsetX;
		dashLengthOffsetX = DashedLength / FMath::Sqrt(k * k + 1);
		dashLengthOffsetY = k * dashLengthOffsetX;
		DashedIntervalOffsetX = DashedInterval / FMath::Sqrt(k * k + 1);
		DashedIntervalOffsetY = k * DashedIntervalOffsetX;
	}
	offsetX *= xAxisDir;
	offsetY *= yAxisDir;
	dashLengthOffsetX *= xAxisDir;
	dashLengthOffsetY *= yAxisDir;
	DashedIntervalOffsetX *= xAxisDir;
	DashedIntervalOffsetY *= yAxisDir;
	float dashLength = 0;
	FVector2D dashStartPoint(0.f + StartPoint.X + offsetX, 0.f + StartPoint.Y + offsetY);
	FVector2D dashEndPoint(0.f + StartPoint.X + dashLengthOffsetX + offsetX,
	                       0.f + StartPoint.Y + dashLengthOffsetY + offsetY);
	while (FMath::Abs(startOffset) + lineLength > dashLength)
	{
		if (IsPointOnLineSegment(StartPoint, EndPoint, dashStartPoint) && IsPointOnLineSegment(
			StartPoint, EndPoint, dashEndPoint))
		{
			result.Emplace(dashStartPoint);
			result.Emplace(dashEndPoint);
		}
		else if (!IsPointOnLineSegment(StartPoint, EndPoint, dashStartPoint) && IsPointOnLineSegment(
			StartPoint, EndPoint, dashEndPoint))
		{
			result.Emplace(StartPoint);
			result.Emplace(dashEndPoint);
		}
		else if (IsPointOnLineSegment(StartPoint, EndPoint, dashStartPoint) && !IsPointOnLineSegment(
			StartPoint, EndPoint, dashEndPoint))
		{
			result.Emplace(dashStartPoint);
			result.Emplace(EndPoint);
		}
		else if (result.Num() != 0)
		{
			break;
		}
		dashStartPoint.X += dashLengthOffsetX + DashedIntervalOffsetX;
		dashStartPoint.Y += dashLengthOffsetY + DashedIntervalOffsetY;
		dashEndPoint.X += dashLengthOffsetX + DashedIntervalOffsetX;
		dashEndPoint.Y += dashLengthOffsetY + DashedIntervalOffsetY;
		dashLength += DashedLength + DashedInterval;
	}

	return result;
}

bool SElementUIBorder::IsPointOnLineSegment(FVector2D StartPoint, FVector2D EndPoint, FVector2D Point) const
{
	return Point.X >= FMath::Min(StartPoint.X, EndPoint.X) && Point.X <= FMath::Max(StartPoint.X, EndPoint.X) && Point.Y
		>= FMath::Min(StartPoint.Y, EndPoint.Y) && Point.Y <= FMath::Max(StartPoint.Y, EndPoint.Y);
}

bool SElementUIBorder::IsPointInside(FVector2D Point, FVector2D BorderSize)
{
	bool result = true;
	float XPosition = BorderSize.X - FilletRadius;
	float YPosition = BorderSize.Y - FilletRadius;
	FVector2D LeftTop = FVector2D(FilletRadius,FilletRadius);
	FVector2D RightTop = FVector2D(XPosition,FilletRadius);
	FVector2D RightBottom = FVector2D(XPosition,YPosition);
	FVector2D LeftBottom = FVector2D(FilletRadius,YPosition);
	float MinDistance = FilletRadius + 10;
	if((Point.X<=FilletRadius||Point.X>=XPosition)&&(Point.Y<=FilletRadius||Point.Y>=XPosition))
	{
		MinDistance = FMath::Min(MinDistance,(Point - LeftTop).Size());
		MinDistance = FMath::Min(MinDistance,(Point - RightTop).Size());
		MinDistance = FMath::Min(MinDistance,(Point - RightBottom).Size());
		MinDistance = FMath::Min(MinDistance,(Point - LeftBottom).Size());
		result = MinDistance > FilletRadius;
	}
	return result;
}

float SElementUIBorder::GetMagicNumber(float angle) const
{
	return 4.f / 3.f * (1 - FMath::Cos(angle/2)) / FMath::Sin(angle/2);
}

int32 SElementUIBorder::DrawSingleBorderLine(FSlateWindowElementList& ElementList, uint32 InLayer,
                                             const FGeometry& AllottedGeometry, const TArray<FVector2D>& Points, const FWidgetStyle& InWidgetStyle) const
{
	TArray<FVector2D> borderDashedLine = LineLerp(Points[0], Points[1], DashedOffset.Get());
	for (int32 j = 0; j < borderDashedLine.Num(); j += 2)
	{
		TArray<FVector2D> dashedLine;
		dashedLine.Emplace(borderDashedLine[j]);
		dashedLine.Emplace(borderDashedLine[j + 1]);
		DrawLine(ElementList, InLayer, AllottedGeometry.ToPaintGeometry(), dashedLine, InWidgetStyle);
	}
	return InLayer;
}

int32 SElementUIBorder::DrawSingleFillet(float Angle, int32 XDirector, int32 YDirector, FSlateWindowElementList& ElementList, uint32 InLayer, const FGeometry& AllottedGeometry, const FWidgetStyle& InWidgetStyle) const
{
	float realFilletRadius = FilletRadius == 0 ? 0: FilletRadius + 1;
	FVector2D realSourcePoint(AllottedGeometry.Size.X / 2.f * -1.f,AllottedGeometry.Size.Y / 2.f * -1.f);
	FVector2D vertexPoint(AllottedGeometry.Size.X / 2.f * XDirector,AllottedGeometry.Size.Y / 2.f * YDirector);
	FVector2D filletSource(vertexPoint.X - XDirector * realFilletRadius,vertexPoint.Y - YDirector * realFilletRadius);
	// filletSource = filletSource - FVector2D(AllottedGeometry.Size.X/2.f*-1.f,AllottedGeometry.Size.Y/2.f*-1.f);
	float magicLength = realFilletRadius * GetMagicNumber(Angle);
	FVector2D P0 = FVector2D(vertexPoint.X, filletSource.Y);
	FVector2D P1 = P0 + FVector2D(0,magicLength * YDirector);
	FVector2D P3 = FVector2D(filletSource.X, vertexPoint.Y);
	FVector2D P2 = P3+FVector2D(magicLength * XDirector,0);
	P0 = P0 - realSourcePoint;
	P1 = P1 - realSourcePoint;
	P3 = P3 - realSourcePoint;
	P2 = P2 - realSourcePoint;
	FSlateDrawElement::MakeCubicBezierSpline(ElementList,InLayer,AllottedGeometry.ToPaintGeometry(),P0,P1,P2,P3,LineThickness * AllottedGeometry.Scale,ESlateDrawEffect::None,LineColor.Get()*InWidgetStyle.GetColorAndOpacityTint());
	return InLayer;
}

#undef LOCTEXT_NAMES
